package utils

import (
	"context"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"net/smtp"
	"strings"
	"time"
)

const (
	dialTLS uint32 = iota + 1
	startTLS
)

type mail struct {
	host        string
	port        string
	username    string
	password    string
	subject     string
	content     string
	contentType string // plain / html
	timeout     time.Duration
	encryption  uint32
}

// MailContact 邮件联系人
type MailContact struct {
	name  string
	email string
}

func NewMailContact(name, email string) *MailContact {
	mc := MailContact{
		name:  name,
		email: email,
	}
	return &mc
}

// String 将邮件联系人结构转写为邮件内容格式
func (c *MailContact) String() string {
	if len(c.name) == 0 {
		return fmt.Sprintf("<%s>", c.email)
	} else {
		return fmt.Sprintf("\"=?utf-8?B?%s?=\"<%s>", base64enc(c.name), c.email)
	}
}

func base64enc(src string) string {
	return base64.StdEncoding.EncodeToString(StringToBytes(src))
}

// buildContent 构造邮件内容, 区分text/plain和text/html
func buildContent(from *MailContact, toList []*MailContact, subject string, content string, ctype string) string {
	builder := strings.Builder{}

	builder.WriteString("MIME-Version: 1.0\n")
	builder.WriteString(fmt.Sprintf("Date: %s\n", time.Now().Format(time.RFC1123Z)))
	builder.WriteString(fmt.Sprintf("From: %s\n", from.String()))
	builder.WriteString("To: ")
	for i, to := range toList {
		if i != 0 {
			builder.WriteString(", ")
		}
		builder.WriteString(to.String())
	}
	builder.WriteString("\n")
	builder.WriteString(fmt.Sprintf("Subject: =?utf-8?B?%s?=\n", base64enc(subject)))
	builder.WriteString(fmt.Sprintf("Content-Type: text/%s; charset=UTF-8\n", ctype))
	builder.WriteString("\n")
	builder.WriteString(content)

	return builder.String()
}

// NewMail 构造新邮件实例
//
//	NewMail().Server(host, port).Auth(username, password).Timeout(5*time.Second).WithTLS().WithHTML().Content(subject, content).Send(sendto, from)
//	构造实例 -> 服务器信息 -> 用户认证信息 -> 设置连接超时(optional) -> 启用SSL(optional) -> 使用text/html(optional) -> 填写邮件内容 -> 投递邮件
func NewMail() *mail {
	return &mail{contentType: "plain"}
}

// Auth 用户身份认证
func (m *mail) Auth(username, password string) *mail {
	m.username = username
	m.password = password
	return m
}

// Content 邮件内容
func (m *mail) Content(subject, content string) *mail {
	m.subject = subject
	m.content = content
	return m
}

// Send 投递邮件
// 如果未启用TLS加密，则调用标准库smtp.SendMail
// 若启用TLS，则建立TLS连接
func (m *mail) Send(sendto []*MailContact, from *MailContact) (err error) {
	// 使用context控制超时时间
	timeout := m.timeout
	if timeout == 0 {
		timeout = 10 * time.Second
	}
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	// 构造邮件内容
	from.email = m.username
	msg := StringToBytes(buildContent(from, sendto, m.subject, m.content, m.contentType))

	// 创建连接
	var conn net.Conn
	conn, err = m.connect(ctx, "tcp")
	if err != nil {
		return
	}
	defer conn.Close()

	// 通过通道接收邮件发送错误消息
	errCh := make(chan error)
	go func() {
		toMailList := make([]string, len(sendto))
		for i, mc := range sendto {
			toMailList[i] = mc.email
		}
		errCh <- m.write(conn, toMailList, msg)
		close(errCh)
	}()

	// 超时机制
	select {
	case <-ctx.Done():
		err = errors.New("connection time out")
	case err = <-errCh:
		// returns the error
	}

	return
}

// Server 邮件服务器
func (m *mail) Server(host, port string) *mail {
	m.host = host
	m.port = port
	return m
}

// Timeout 指定连接超时时间，默认10秒
func (m *mail) Timeout(timeout time.Duration) *mail {
	m.timeout = timeout
	return m
}

// WithHTML 使用text/html而不是text/plain
func (m *mail) WithHTML() *mail {
	m.contentType = "html"
	return m
}

// WithStartTLS 在连接中使用StartTLS
func (m *mail) WithStartTLS() *mail {
	m.encryption = startTLS
	return m
}

// WithTLS 使用SSL
func (m *mail) WithTLS() *mail {
	m.encryption = dialTLS
	return m
}

// connect 创建连接
func (m *mail) connect(ctx context.Context, network string) (conn net.Conn, err error) {
	if m.encryption == dialTLS {
		d := &tls.Dialer{
			Config: &tls.Config{ServerName: m.host},
		}
		conn, err = d.DialContext(ctx, network, m.host+":"+m.port)
	} else {
		d := &net.Dialer{}
		conn, err = d.DialContext(ctx, network, m.host+":"+m.port)
	}

	return
}

// write 向连接中写入邮件内容
func (m *mail) write(conn net.Conn, sendto []string, msg []byte) (err error) {
	client, err := smtp.NewClient(conn, m.host)
	if err != nil {
		return
	}
	// quit will send a QUIT command and close the connection
	defer client.Quit()

	// StartTLS
	if m.encryption == startTLS {
		err = client.StartTLS(&tls.Config{ServerName: m.host})
		if err != nil {
			return
		}
	}

	var auth smtp.Auth
	if m.encryption == dialTLS || m.encryption == startTLS {
		// PlainAuth
		auth = smtp.PlainAuth("", m.username, m.password, m.host)
	} else {
		// 非加密方式需要使用自定义Auth
		auth = &loginAuth{m.username, m.password}
	}

	// smtp auth command
	err = client.Auth(auth)
	if err != nil {
		return
	}

	// smtp mail command
	err = client.Mail(m.username)
	if err != nil {
		return
	}

	// smtp rcpt command
	for _, to := range sendto {
		if err = client.Rcpt(to); err != nil {
			return
		}
	}

	// smtp data command
	w, err := client.Data()
	if err != nil {
		return
	}
	defer w.Close()

	if _, err = w.Write(msg); err != nil {
		return
	}

	return
}

// ################
// custom LOGIN Auth against smtp.Auth
// ################

type loginAuth struct {
	username, password string
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", nil, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return StringToBytes(a.username), nil
		case "Password:":
			return StringToBytes(a.password), nil
		default:
			return nil, errors.New("unknown server cmd")
		}
	}
	return nil, nil
}
